

/**
 * 检测传入的参数类型
 * @param obj {All}	需要进行参数检测的对象
 * @return {String} 所属类型字符串
 */
function typeOf (obj) {
    const toString = Object.prototype.toString;
    const map = {
        '[object Boolean]'	 : 'boolean',
        '[object Number]' 	 : 'number',
        '[object String]' 	 : 'string',
        '[object Function]'  : 'function',
        '[object Array]' 	 : 'array',
        '[object Date]' 	 : 'date',
        '[object RegExp]'	 : 'regExp',
        '[object Undefined]' : 'undefined',
        '[object Null]' 	 : 'null',
        '[object Object]' 	 : 'object'
    };
    return map[toString.call(obj)];
}


/**
 * 自定义事件( 单例模式 )
 */
export const evt = (function() {
    let _evt = null

    function _EventTarget(){
        // 构造函数的容错处理
        if(this instanceof _EventTarget) {
            if(!this.handles) this.handles = {};
        }
        else {
            new _EventTarget();
        }
    }
    _EventTarget.prototype = {
        constructor : _EventTarget,
        // 注册事件，注意，这块注册的函数是否可以写成箭头函数，写成箭头函数的时候，在trigger阶段绑定this执行是否会有问题，有待研究
        addHandle : function(type,handler){
            var type = type.toString(),
                handlesArr = this.handles[type];
            if(typeof handlesArr === 'undefined')handlesArr = this.handles[type] = [];
            if(typeof handler === 'function')handlesArr.push(handler);
        },

        // 移除事件
        removeHandler : function(type,handler){
            var type = type.toString(),
                handlesArr = this.handles[type],i,len;
            if(typeof handler === 'undefined'){
                this.handles[type] = void(0);
                return ;
            }
            if(Array.isArray(handlesArr)){
                for(i=0,len=handlesArr.length;i<len;i++){
                    if(handlesArr[i]===handler){
                        handlesArr.splice(i,1);
                        break;
                    }
                }
            }
        },

        // 触发事件
        trigger : function(evt){
            var event = typeof evt === 'object' ? evt : { type: evt.toString() },
                type = event.type ? event.type.toString() : '',
                params = event.params && Array.isArray(event.params) ? event.params : [],
                handlesArr = this.handles[type];

            if(!event.target){
                event.target = this;
            }
            if(Array.isArray(handlesArr)){
                for(var i=0,len=handlesArr.length;i<len;i++){
                    handlesArr[i].apply(event.target,params);
                }
            }
        }
    };

    /**
     * 惰性单例，返回的是一个待执行的函数，函数执行再实例化_evt

     return function () {
	        if (!_evt) {
	            _evt = new _EventTarget()
	        }
	        return _evt
	    };

     */

    if (_evt && _evt instanceof _EventTarget) {
        // do nothing
    }
    else {
        _evt = new _EventTarget()
    }
    return _evt
})()

/**
 * 判空函数
 * @param  {obj/arr/str}  检测对象
 */
function empty(obj){
    if(typeof obj === "object"){
        if(Array.isArray(obj)){			// array
            return !obj.length>0
        }else{							// object
            return !(function(obj){
                    var key,
                        len = 0;
                    for (key in obj){
                        len = ++len;
                    }
                    return len;
                })(obj)>0;
        }
    }else if(typeof obj === "string"){	// string
        return !(obj.trim()).length>0
    }else{								// error
        throw new Error("empty函数接收的参数类型：对象、数组、字符串");
    }
}



/**
 * 数组去空【空字符串 null undefined】
 * @param arr Array 需要处理的原始数组
 * @return Array 处理后的数组对象
 *
 */
function arrayDeleteEmpty(arr) {
    let clean = []
    let target = 'null undefined'

    for (let i = 0, len = arr.length; i < len; i++) {
        let item = arr[i]
        let type = typeOf(item)
        if (type === 'string' && empty(item) || target.indexOf(type) >= 0)
            continue
        clean.push(item)
    }
    return clean
}


/**
 * 数组扩展方法，用于找到第一个符合条件的数组成员。
 * @param  {Function} 执行筛选的回调函数
 * @return {Object}   筛选出来的对象
 *
 *
 * Test:
 * [1,2,3,4,5,-10,20,120,-8].find((item, index, arr) => { return item < 0 })
 *
 *
 * Expect:
 * -10
 *
 */
(() => {
    // 支持ES6原生find方法就用原生
    if (Array.prototype.find) return;

    // 这块注意不能写成箭头函数形式
    // 由于箭头函数没有自己的this，如果写成箭头函数，this直接指向window
    Array.prototype.find = function(callBack) {
        for (let i = 0; i < this.length; i++) {
            if (callBack(this[i], i, this)) return this[i]
        }
    }

})()



/**
 * 对象深度查找，这个递归重要的一匹，妈的要不是封装了这个递归这个项目早就炸了。。。 (╯°Д°)╯︵ ┻━┻
 * @param target    {Object}   需要处理的原始对象
 * @param callback  {Function} filter函数，接收一个参数，即当前调用环境对象
 * @returns         {Object}   符合筛选条件的对象
 */
function findDeeply(target, callback) {
    const flag = typeOf(target)
    let result
    if (flag === 'array') {
        for (let i = 0, item; item=target[i++];) {
            result = findDeeply(item, callback)
            if (result) return result
        }
    }
    else if (flag === 'object') {
        if (callback(target)) {
            return target
        }
        for (let k in target) {
            result = findDeeply(target[k], callback)
            if (result) return result
        }
    }
}




/**
 * 生成指定长度的随机字符串
 * @param len
 * @returns {string}
 */
function random_string(len) {
    len = len || 32
    let chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678',
        maxPos = chars.length,
        pwd = ''
    for (let i = 0; i < len; i++) {
        pwd += chars.charAt(Math.floor(Math.random() * maxPos));
    }
    return pwd;
}


/**
 * 获取文件后缀
 * @param filename    {String}  文件名
 * @param separator   {Boolean} 是否要分隔符，默认需要
 * @returns {string}
 */
function get_suffix(filename, separator=true) {
    let pos = separator ? filename.lastIndexOf('.') : filename.lastIndexOf('.') + 1,
        suffix = ''
    if (pos != -1) {
        suffix = filename.substring(pos)
    }
    return suffix;
}


/**
 * 获取 1970/1/1 午夜距离该日期时间的秒数
 * @returns {number}
 * @private
 */
function getParseDate() {
    return Date.parse(new Date()) / 1000
}


/**
 * 用于过滤Vue处理过的对象
 * @param obj
 * @returns {{}}
 */
function objFilter(obj) {
    let ret = {}
    for (let k in obj) {
        ret[k] = obj[k]
    }
    return ret
}

/**
 * 格式化时间戳函数
 * @param fmt 转换模式
 * @return 转换后的时间字符串
 *
 *
 * Test1:
 * var time1 = new Date().format("yyyy-MM-dd hh:mm:ss");
 *
 * Expect1:
 * "2018-01-16 13:29:00"
 *
 *
 *
 *
 * Test2:
 * var time2 = new Date().format("yyyyMMddhhmmss");
 *
 * Expect2:
 * "20180116132849"
 *
 *
 */
Date.prototype.format = function(fmt) {
    let o = {
        "M+" : this.getMonth()+1,                 //月份
        "d+" : this.getDate(),                    //日
        "h+" : this.getHours(),                   //小时
        "m+" : this.getMinutes(),                 //分
        "s+" : this.getSeconds(),                 //秒
        "q+" : Math.floor((this.getMonth()+3)/3), //季度
        "S"  : this.getMilliseconds()             //毫秒
    };
    if(/(y+)/.test(fmt)) {
        fmt=fmt.replace(RegExp.$1, (this.getFullYear()+"").substr(4 - RegExp.$1.length));
    }
    for(let k in o) {
        if(new RegExp("("+ k +")").test(fmt)){
            fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length)));
        }
    }
    return fmt
}

/**
 * 计算折扣函数，此函数属于业务工具函数，不具有通用性
 * @param originNum   折扣小数
 * @param x           保留位数【选传】
 * @returns {Sting}   处理完成后带 % 号的折扣字符串
 */
function calcDiscount(originNum, x) {
    let discString = originNum * 100 + '',
        list = discString.split('.'),
        disc
    if (list[0] === '100') {
        disc = '100%'
        return disc
    }
    if (list[1]) {
        list[1] = list[1].substr(0, Number(x) || 0)
        disc = list.join('.') + '%'
    } else {
        disc = list.join('') + '%'
    }
    return disc
}


/**
 * 数组提取，不改变原数组
 * @param  {Array}  arr   需要进行操作的原始数组
 * @param  {Number} start 截取的初始位置
 * @param  {Number} num   需要截取的个数
 * @return {Array}        截取完成后返回的新数组
 *
 * Test:
 * [1,2,32,'sds','asd',90,'piis']
 *
 *
 */
function copypart(arr, start, num) {
    let ret = []
    if (typeof num === 'number') {
        ret = arr.slice(start, start + num)
    } else {
        ret = arr.slice(start)
    }
    return ret
}

/**
 * 引用比较，用于将两个数组结构的每一项两两比较，通过筛选函数筛选出双方符合条件的、第一次找到的一对索引
 * @param target1           数组1
 * @param target2           数组2
 * @param filter            筛选函数，接收两项，需要返回一个布尔值
 * @param cb                筛选函数返回true执行的回调函数，接收两个索引
 * @returns {Object/null}   装有一对索引的对象或者是null
 */
function refCompare(target1, target2, filter, cb) {
    for (let i = 0, item; i < target1.length; i++) {
        item = target1[i]
        for (let k = 0, part; k < target2.length; k++) {
            part = target2[k]
            if (filter(item, part)) cb && cb(i, k)
        }
    }
}

/**
 * 对象属性去空/undefined/null
 * @param target  {Object}         需要处理的原始对象，看好是“对象”
 * @param map     {Array}          map映射，target中需要处理的key字符串
 * @returns 	  {Object}         处理后的对象
 */
function ObjFilterEmptyField(target, map) {
    let ret = {}
    map.forEach(key => {
        if (target[key] !== '' && target[key] != null) ret[key] = target[key]
    })
    return ret
}


/**
 * 对象去空
 * @param target  需要处理的目标对象
 * @returns {Object}  处理后的对象
 */
function deleteObjEmpty(target) {
    let ret = {}
    for (let k in target) {
        if (target[k] !== '' && target[k] != null) ret[k] = target[k]
    }
    return ret
}

/**
 * 脚本动态处理，动态插入或者动态卸载
 * @param src           {String}    需要加载脚本的 src 地址
 * @param options       {Object}    配置选项
 *          isLoad      {Boolean}   true 表示加载该脚本，false 表示卸载该脚本，默认为true
 *          callBack    {Function}  加载/卸载完成后的回调
 * @constructor
 */
function dynamicScript(src, options) {
    src = String(src)
    options = Object.assign({ isLoad: true }, options)
    const scriptDoms = document.getElementsByTagName('script')
    // 存储找到的 scriptDom 节点
    let targetScriptDom = null
    let { isLoad, callBack } = options
    // 是否存在匹配该 src 的 script
    let hasTargetScript = _checkRepet([...scriptDoms])
    let script = document.createElement('script')
    script.type = 'text/javascript'

    // 监听 onload 事件
    _EVTinit(script)

    // 如果不存在这个script，并且 isLoad 为 true 再插入
    if (!hasTargetScript && isLoad) {
        script.src = String(src)
        document.body.appendChild(script)

    // 如果存在这个script，并且 isLoad 为 false 则卸载
    } else if (hasTargetScript && !isLoad) {
        document.body.removeChild(targetScriptDom)
    }

    function _EVTinit(script) {
        script.onload = script.onreadystatechange = function() {
            if(!this.readyState || this.readyState=='loaded' || this.readyState=='complete') {
                callBack && callBack()
            }
        }
    }

    function _checkRepet(domList) {
        let item = null, result = false
        for (let i = 0; i < domList.length; i++) {
            item = domList[i]
            if (item.src === src) {
                targetScriptDom = domList[i]
                result = true
                break
            }
        }
        return result
    }
}


/*
 * 判断元素是否有某个className
 * @param el {object} 目标元素
 * @param className {string} 需要检测的className
 * @return Boolean
 */
function hasClass (el, className) {
    var reg = new RegExp('(^|\\s)' + className + '(\\s|$)');
    return reg.test(el.className)
}

/**
 * 判断一个元素是否在目标对象中
 * @param target   待判断的元素
 * @param arr      目标对象
 * @returns {boolean}
 */
function inArray(target, arr) {
    for (let i = 0,item; i < arr.length; i++) {
        item = arr[i]
        if (target === item) return true
    }
    return false
}


export { typeOf, empty, arrayDeleteEmpty, findDeeply, random_string, get_suffix, getParseDate, objFilter, calcDiscount, copypart, refCompare, ObjFilterEmptyField, deleteObjEmpty, dynamicScript, hasClass, inArray }